#pragma once
#include <Common/Types.h>
#include <memory.h>
#include <string.h>

#include <iostream>
#include <memory>

#include "Common/Logger.h"

namespace keeper_server {

  class MySQLPacket {
   public:
    static const unsigned default_buff_size = 1024;
    MySQLPacket(unsigned long size = default_buff_size) : buff_size(size) {
      buff = new unsigned char[size];
      read_pos = buff;
      write_pos = buff;
    }
    ~MySQLPacket() {
      if (buff) {
        delete[] buff;
      }
    }

    UInt32 length() { return write_pos - read_pos; }

    void resetPacket() {
      memset(buff, 0, buff_size);
      read_pos = buff;
      write_pos = buff;
    }

    void packNChar(const void* value, unsigned int n) {
      LOG_TRACE("write_pos:%d, n:%d, buff_size:%d",
                (unsigned int)(write_pos - buff), n, buff_size);
      if ((unsigned int)(write_pos - buff + n) > buff_size) {
        throw "buff has full";
      }
      memcpy(write_pos, value, n);
      write_pos += n;
    }

    void packChar(char value) { packNChar(&value, 1); }
    void packUInt8(const UInt8 value) { packNChar(&value, 1); }
    void packUInt16(const UInt16 value) { packNChar(&value, 2); }
    void packUInt24(const UInt32 value) { packNChar(&value, 3); }
    void packUInt32(const UInt32 value) { packNChar(&value, 4); }
    void packUInt64(const UInt32 value) { packNChar(&value, 8); }

    void fill(const char value, unsigned int n) {
      LOG_TRACE("write_pos:%d, n:%d, buff_size:%d",
                (unsigned int)(write_pos - buff), n, buff_size);
      if ((unsigned int)(write_pos - buff + n) > buff_size) {
        throw "buff has full";
      }
      memset(write_pos, value, n);
      write_pos += n;
    }

    UInt8 packLenencInt(UInt64 value) {
      if (value < 251ULL) {
        packNChar(&value, 1);
        return 1;
      } else if (value < 65536ULL) {
        packUInt8(252);
        packNChar(&value, 2);
        return 3;
      } else if (value < 16777216ULL) {
        packUInt8(253);
        packNChar(&value, 3);
        return 4;
      } else {
        packUInt8(254);
        packNChar(&value, 8);
        return 9;
      }
    }

    void packStr(const void* value, unsigned int n) { packNChar(value, n); }
    void packStr(const void* value) {
      auto n = strlen((char*)value) + 1;
      packNChar(value, n);
    }
    void packLenencStr(String& value) {
      auto len = value.length();
      packLenencStr(value.c_str(), len);
    }

    void packLenencStr(const char* value) {
      auto len = strlen(value);
      packLenencStr(value, len);
    }
    void packLenencStr(const char* value, size_t len) {
      packLenencInt(len);
      packStr(value, len);
    }

    void unpackUInt8(UInt8* value) {
      *value = *(UInt8*)(read_pos);
      read_pos += 1;
    }
    void unpackUInt16(UInt16* value) {
      *value = *(UInt16*)(read_pos);
      read_pos += 2;
    }
    void unpackUInt24(UInt32* value) {
      char tmp[4];
      memset(tmp, 0, 4);
      memcpy(tmp, read_pos, 3);
      *value = *(UInt32*)(tmp);
      read_pos += 3;
    }

    void unpackUInt32(UInt32* value) {
      *value = *(UInt32*)(read_pos);
      read_pos += 4;
    }
    void unpackUInt64(UInt64* value) {
      *value = *(UInt64*)(read_pos);
      read_pos += 8;
    }

    void unpackStr(char* value, unsigned int len) {
      if (value) {
        memcpy(value, read_pos, len);
        read_pos += len;
      }
    }
    void unpackStr(char* value) {
      if (value) {
        unsigned char* tmp = read_pos;
        auto len = strlen((char*)tmp) + 1;
        memcpy(value, (char*)read_pos, len);
        read_pos += len;
      }
    }
    void unpackStr(String& value) {
      unsigned char* tmp = read_pos;
      auto len = strlen((char*)tmp) + 1;
      value.assign((char*)(tmp));
      read_pos += len;
    }
    void unpackStr(String& value, size_t len) {
      unsigned char* tmp = read_pos;
      value.assign((char*)(tmp), len);
      read_pos += len;
    }

    void unpackLenecInt(UInt64* value) {
      UInt8 r;
      unpackUInt8(&r);
      switch (r) {
        case 251:
          *value = static_cast<UInt64>(r);
          break;
        case 252: {
          UInt16 tmp;
          unpackUInt16(&tmp);
          *value = static_cast<UInt64>(tmp);
        } break;
        case 253: {
          UInt32 tmp;
          unpackUInt24(&tmp);
          *value = static_cast<UInt32>(tmp);
        } break;
        case 254: {
          unpackUInt64(value);
        } break;
        default: {
          *value = static_cast<UInt64>(r);
        } break;
      }
    }

    void unpackLenecStr(String& value) {
      UInt64 len;
      unpackLenecInt(&len);
      if (len == static_cast<UInt64>(~0)) {
        value.assign("");
        return;
      }
      unpackStr(value, len);
    }

    void skip(UInt16 len) { read_pos += len; }

    unsigned char* buff;
    unsigned long buff_size;
    unsigned char* read_pos;
    unsigned char* write_pos;
  };


}
